// RedJey Graphics User Interface Library for J2ME Platform.
// Copyright (C) 2008 Yevgen Krapiva
//
// This library is free software; you can redistribute
// it and/or modify it under the terms of the
// GNU Lesser General Public License as published
// by the Free Software Foundation; either version 3.0
// of the License, or any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package com.incrediblesoft.redjey;

/**
 * @author Yevgen Krapiva<br>
 *         Homepage: http://code.google.com/p/redjey/<br>
 *         Feedback: ykrapiva@gmail.com<br>
 *         Copyright (C) 2008 Yevgen Krapiva<br>
 */

import com.incrediblesoft.redjey.event.*;

import javax.microedition.lcdui.Graphics;
import java.util.Hashtable;
import java.util.Vector;

abstract public class Component
{
    /**
     * Layout constants
     */
    public static final int LEFT_ALIGNMENT =  1;
    public static final int RIGHT_ALIGNMENT =  2;
    public static final int TOP_ALIGNMENT =  4;
    public static final int BOTTOM_ALIGNMENT =  8;
    public static final int HCENTER_ALIGNMENT = 16;
    public static final int VCENTER_ALIGNMENT = 32;
    /**
     * Set this flag to say layout manager that this component is willing to have max available width;
     */
    public static final int H_EXPAND = 64;
    /**
     * Set this flag to say layout manager that this component is willing to have max available height;
     * Not implemented yet.
     */
    public static final int V_EXPAND = 128;

    /**
     * Parent, it is container
     */
    protected Container parent;

    /**
     * Coordinates in a parent's coordinate system (from left top edge of client area)
     */
    protected int x, y;

    /**
     * Component alignment
     */
    protected int alignment = LEFT_ALIGNMENT | TOP_ALIGNMENT;

    /**
     * Component content alignment
     */
    protected int contentAlignment = LEFT_ALIGNMENT | TOP_ALIGNMENT;

    /**
     * Width and height of component
     */
    protected int width, height;

    /**
     * Preferred fixed component size
     */
    protected Dimension preferredFixedSize;

    /**
     * Width and height of client area
     */
    protected Dimension clientSize;

    /**
     * Width and height of content area
     */
    protected Dimension contentSize;

    /**
     * Component insets. First element [0] - left, [1] - right, [2] - top, [3] - bottom
     */
    protected int [] insets = new int[4];

    /**
     * Visibility flag
     */
    protected boolean isVisible;

    /**
     * Focusable flag
     */
    protected boolean isFocusable;

    /**
     * Focus flag
     */
    protected boolean isFocused;

    /**
     * Enabled flag
     */
    protected boolean isEnabled;
        
    /**
     * String tag
     */
    private String tag;

    /**
     * Component style class
     */
    protected Style[] styleSet;

    /**
     * Current style
     */
    protected Style currentStyle;

    /**
     * Action listener
     */
    protected Vector actionListeners;

    /**
     * Mouse listeners
     */
    protected Vector mouseListeners;

    /**
     * Focus listeners
     */
    protected Vector focusListeners;

    /**
     * Key listeners
     */
    protected Vector keyListeners;

    /**
     * Validation flag
     */
    protected boolean validated;

    /**
     * Predefined component states
     */
    public static final int NORMAL = 0;
    public static final int FOCUSED = 1;
    public static final int PRESSED = 2;
    public static final int DISABLED = 3;
    public static final int TOTAL_STATES = 4;

    /**
     * Component state.
     */
    protected int state = NORMAL;
    protected int prevState;

    // Variables for safe clipping and translation
    private int oldTransX, oldTransY;
    private int oldClipX, oldClipY;
    private int oldClipWidth, oldClipHeight;

    /**
     * Constructs component
     */
    public Component()
    {
        this(UIManager.DEFAULT_SET);
    }

    /**
     * Constructs component with a given style class
     * @param styleClassName style class
     */
    public Component(String styleClassName)
    {
        super();

        clientSize = new Dimension();
        contentSize = new Dimension();

        if (styleClassName == null)
            styleClassName = UIManager.DEFAULT_SET;

        setStyleClass(styleClassName);
        setVisible(true);
        setEnabled(true);
    }

    /**
     * This method is used to retrieve the appropriate style set from a hashtable of styles
     * @return Name of component
     */
    abstract public String getName();

    /**
     * Returns component's parent
     * @return parent component
     */
    public Component getParent()
    {
        return parent;
    }

    /**
     * Returns the width of component
     * @return width
     */
    public int getWidth()
    {
        return width;
    }

    /**
     * Returns the height of component
     * @return height
     */
    public int getHeight()
    {
        return height;
    }

    /**
     * Sets the size of component
     * @param size new size
     */
    public void setSize(Dimension size)
    {
        setSize(size.width, size.height);
    }

    /**
     * Sets the size of component
     * @param width width
     * @param height height
     */
    public void setSize(int width, int height)
    {
        this.width = width;
        this.height = height;
        validate();
    }

    /**
     * Sets component location relative to parent's coordinates.
     * @param x x
     * @param y y
     */
    public void setLocation(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public void setBounds(int x, int y, int width, int height)
    {
        setLocation(x, y);
        setSize(width, height);
    }

    public void setX(int x)
    {
        this.x = x;
    }

    public void setY(int y)
    {
        this.y = y;
    }

    public int getX()
    {
        return x;
    }

    public int getY()
    {
        return y;
    }

    /**
     * Returns X-coordinate in device's screen coordinate system
     * @return X-coordinate
     */
    public int getAbsoluteX()
    {
        return (parent != null ? parent.getAbsoluteX() : 0) + x;
    }

    /**
     * Returns Y-coordinate in device's screen coordinate system
     * @return Y-coordinate
     */
    public int getAbsoluteY()
    {
        return (parent != null ? parent.getAbsoluteY() : 0) + y;
    }

    public Rectangle getBounds()
    {
        return new Rectangle(x, y, width, height);
    }

    public Rectangle getAbsoluteRect()
    {
        return new Rectangle(getAbsoluteX(), getAbsoluteY(), width, height);
    }

    /**
     * Returns client's area dimension
     * @return Dimension
     */
    public Dimension getClientSize()
    {
        return clientSize;
    }

    /**
     * Returns the size of content area. Used to adjust content alignment
     * @return Content size
     */
    protected Dimension getContentSize()
    {
        return contentSize;
    }

    /**
     * Returns component insets.
     * First element [0] - left, [1] - right, [2] - top, [3] - bottom
     * @return insets
     */
    public int[] getInsets()
    {
        return insets;
    }

    /**
     * Calculates client area dimension. Invoked by validate() method
     */
    protected void calcClientSize()
    {
        clientSize.width = getWidth() - insets[0] - insets[1];
        clientSize.height = getHeight() - insets[2] - insets[3];
    }

    /**
      * Calculates client area dimension. Invoked by validate() method
      */
     protected void calcContentSize()
     {
         contentSize.width = getWidth() - insets[0] - insets[1];
         contentSize.height = getHeight() - insets[2] - insets[3];
     }

    /**
     * Returns preferred size. Preferred size is used by Layout managers
     * @return Dimension
     */
    public Dimension getPreferredSize()
    {
        //If we have manually assigned preferred getComponentCount the return it
        if (preferredFixedSize != null) return preferredFixedSize;

        //Otherwise move along all styles and calculate the maximum preffered getComponentCount
        else if (styleSet.length > 0)
        {
            int maxWidth = 0;
            int maxHeight = 0;
            Dimension dim = null;

            for (int i=0; i<styleSet.length; i++)
            {
                if (styleSet[i] == null) continue;

                dim = calcPreferredSize(styleSet[i].paddingHorizontal, styleSet[i].paddingVertical, styleSet[i].font, styleSet[i].attributes);
                dim.width += styleSet[i].getMinWidth();
                dim.height += styleSet[i].getMinHeight();

                maxWidth = Math.max(maxWidth, dim.width);
                maxHeight = Math.max(maxHeight, dim.height);
            }

            dim.width = maxWidth;
            dim.height = maxHeight;

            return dim;
        }

        return null;
    }

    /**
     * Calculate preferred component size depending on given style.
     * Subclasses must implement this method.
     * @param paddingHorizontal
     * @param paddingVertical
     * @param font
     * @param attributes @return The size that component wishes to have
     * @return desirable size
     */
    protected abstract Dimension calcPreferredSize(int paddingHorizontal, int paddingVertical, CustomFont font, Hashtable attributes);

    /**
     * Sets the predefined preferred dimensions.
     * @param dim Dimension
     */
    public void setPreferredSize(Dimension dim)
    {
        preferredFixedSize = dim;
    }

    /**
     * Sets component alignment hints. Used by Layout managers.
     * These hints say how to layout component.
     * @param anchor See *_LAYOUT constants
     */
    public Component setAlignment(int anchor)
    {
        alignment = anchor;
        return this;
    }

    /**
     * Returns component alignment hints
     * @return Alignment hints
     */
    public int getAlignment()
    {
        return alignment;
    }

    /**
     * Sets content alignment hints.
     * These hints say paint() method how to draw content within component client area if its size is bigger then preffered one.
     * @param anchor See *_LAYOUT constants
     */
    public void setContentAlignment(int anchor)
    {
        contentAlignment = anchor;
    }

    /**
     * Returns content alignment hints
     * @return Alignment hints
     */
    public int getContentAlignment()
    {
        return contentAlignment;
    }

    /**
     * Sets component state (NORMAL, FOCUSED, DISABLED or custom one).
     * It is also changes appearance styles.
     * @param state Component state index
     */
    public void setState(int state)
    {
        prevState = this.state;
        this.state = state;

        if (state >=0 && state < styleSet.length)
        {
            if (styleSet[state] != null)
            {
                currentStyle = styleSet[state];
                insets[0] = currentStyle.getLeftInset();
                insets[1] = currentStyle.getRightInset();
                insets[2] = currentStyle.getTopInset();
                insets[3] = currentStyle.getBottomInset();
            }
        }
    }

    /**
     * Returns current state
     * @return state
     */
    public int getState()
    {
        return state;
    }

    /**
     * Returns the current set of styles.
     * @return Set of styles
     */
    public Style[] getStyleClass()
    {
        return styleSet;
    }

    /**
     * Sets the set of styles of the corresponding class.
     * @throws NullPointerException If suitable style class cannot be found in repository.
     * @param className The name of style class.
     */
    public void setStyleClass(String className)
    {
        //Get templates
        Style[] templateSet = UIManager.getStyleClass4Sure(getName(), className);

        //clone them
        styleSet = new Style[templateSet.length];
        for (int i=0; i<styleSet.length; i++)
            if (templateSet[i] != null) styleSet[i] = templateSet[i].clone();

        //update state
        setState(state);
    }

    public void setStyle(Style style)
    {
        setStyle(style, NORMAL);
    }

    /**
     * Sets the style that will represent specified component's state
     * @param style style to set
     * @param state state to represent
     */
    public void setStyle(Style style, int state)
    {
        if (styleSet.length <= state)
        {
            Style[] oldStyles = styleSet;
            styleSet = new Style[Math.max(styleSet.length, state+1)];
            for (int i=0; i<oldStyles.length; i++)
                styleSet[i] = oldStyles[i];
        }

        styleSet[state] = style;

        isFocusable = styleSet.length > FOCUSED && styleSet[FOCUSED] != null;

        setState(state);
    }

    /**
     * Returns current active style
     * @return style
     */
    public Style getCurrentStyle()
    {
        return currentStyle;
    }

    /**
     * Returns style associated with a given component's state
     * @param state component state
     * @return style
     */
    public Style getStyle(int state)
    {
        return (state < styleSet.length ? styleSet[state] : null);
    }

    /**
     * Set this component visible or not
     * @param visible true / false
     */
    public void setVisible(boolean visible)
    {
        this.isVisible = visible;
    }

    /**
     * Returns weather component is visible or not
     * @return true / false
     */
    public boolean isVisible()
    {
        return isVisible;
    }

    /**
     * Dispatches event to children
     * @param event Event to dispatch
     */
    public void dispatchEvent(AbstractEvent event)
    {
        if (isVisible && event.getSource() == this)
            processEvent(event);
    }

    /**
     * Processes event
     * @param e Event
     */
    protected void processEvent(AbstractEvent e)
    {
        switch(e.getEventType())
        {
            case AbstractEvent.MOUSE_EVENT:
                MouseEvent mouseEvent = (MouseEvent) e;
                switch(mouseEvent.getID())
                {
                    case MouseEvent.MOUSE_PRESSED:
                    case MouseEvent.MOUSE_RELEASED:
                    case MouseEvent.MOUSE_CLICKED:
                        processMouseEvent(mouseEvent);
                    break;

                    case MouseEvent.MOUSE_SCROLL_VERTICAL:
                    case MouseEvent.MOUSE_SCROLL_HORIZONTAL:
                        processMouseScrollEvent(mouseEvent);
                        break;
                }
                break;

            case AbstractEvent.FOCUS_EVENT:
                processFocusEvent((FocusEvent) e);
                break;

            case AbstractEvent.KEY_EVENT:
                KeyEvent keyEvent = (KeyEvent) e;
                processKeyEvent(keyEvent);
                break;
        }
    }

    /**
     * Process mouse related events
     * @param event Event
     */
    protected void processMouseEvent(MouseEvent event)
    {
        switch(event.getID())
        {
            case MouseEvent.MOUSE_PRESSED:
                //System.out.println(""+toString()+"->Mouse pressed");
                if (isVisible && isEnabled)
                {
                    if (isFocusable())
                        requestFocus();

                    if (state != PRESSED)
                    {
                        setState(PRESSED);
                        repaint();
                    }

                }
                dispatchEventToListeners(event);
                break;

            case MouseEvent.MOUSE_RELEASED:
                //System.out.println(""+toString()+"->Mouse released");
                if (isEnabled && isVisible)
                {
                    if (isFocused)
                    {
                        if (state != FOCUSED)
                        {
                            setState(FOCUSED);
                            repaint();
                        }
                    }
                    else if (state != NORMAL)
                    {
                        setState(NORMAL);
                        repaint();
                    }
                }
                dispatchEventToListeners(event);
                break;

            case MouseEvent.MOUSE_CLICKED:
                //System.out.println(""+toString()+"->Mouse clicked");
                processClickAction();
                dispatchEventToListeners(event);
                //System.out.println(""+toString()+"->Mouse clicked");
                break;

            default:
                return; // skipping event consuming
        }

        event.consume();
    }

    /**
     * Processes scrolling events
     * @param event Event
     */
    protected void processMouseScrollEvent(MouseEvent event)
    {
        switch(event.getID())
        {
            case MouseEvent.MOUSE_SCROLL_VERTICAL:
            case MouseEvent.MOUSE_SCROLL_HORIZONTAL:
                dispatchEventToListeners(event);
                break;
        }
    }


    /**
     * Processes focus events
     * @param event Focus event
     */
    protected void processFocusEvent(FocusEvent event)
    {
        switch(event.getID())
        {
            case FocusEvent.FOCUS_GAINED:
                this.isFocused = true;
                // System.out.println(""+toString()+" focus GAINED");
                break;

            case FocusEvent.FOCUS_LOST:
                this.isFocused = false;
                //System.out.println(""+toString()+" focus LOST");
                break;
        }

        dispatchEventToListeners(event);
    }

    /**
     * Processes key events
     * @param event Event
     */
    protected void processKeyEvent(KeyEvent event)
    {
        //System.out.println("Component "+toString()+" recieved key event type = "+event.getID() +" keycode = "+event.getKeyCode());
        dispatchEventToListeners(event);
    }

    public void doClick()
    {
        UIManager.postEvent(new MouseEvent(this, MouseEvent.MOUSE_CLICKED));
    }

    protected void processClickAction()
    {
        fireActionEvent();
    }
    /**
     * Generates and dispatched ActionEvent to listeners
     */
    public void fireActionEvent()
    {
        dispatchEventToListeners(new ActionEvent(this));
    }

    /**
     * Returns the focus state of component
     * @return true if component is focused, false otherwise
     */
    public boolean isFocused()
    {
        return state == PRESSED;
    }

    public boolean isEnabled()
    {
        return isEnabled;
    }

    public void setEnabled(boolean enabled)
    {
        this.isEnabled = enabled;
    }

    public boolean isFocusable()
    {
        return isFocusable & ( styleSet.length > FOCUSED && styleSet[FOCUSED] != null );
    }

    public void setFocusable(boolean focusable)
    {
        this.isFocusable = focusable;
    }

    /**
     * Reset validation flag.
     * This means that component needs to be validated again.
     * If not validated manually, not validated component will
     * be validated during the next call to tick() method.
     */
    public void invalidate()
    {
        validated = false;
    }

    /**
     * Invalidates all component tree hierarchy
     */
    public void invalidateTree()
    {
        if (parent != null)
            parent.invalidateTree();

        validated = false;
    }

    /**
     * Validate component
     */
    public void validate()
    {
        //System.out.println("validate -> "+this);

        //Calculate client area dimensions
        calcClientSize();

        insets[0] = currentStyle.getLeftInset();
        insets[1] = currentStyle.getRightInset();
        insets[2] = currentStyle.getTopInset();
        insets[3] = currentStyle.getBottomInset();

        //Call component's custom validation function
        validateComponent();

        //Calculate inner content getComponentCount for proper content alignment in paint() method.
        calcContentSize();

        validated = true;
    }

    /**
     * Validate component.
     * Subclasses must override this method to do custom validation procedure
     */
    protected abstract void validateComponent();

    /**
     * Adds action listener to this component.
     * @param listener action listener
     */
    public void addActionListener(ActionListener listener)
    {
        if (actionListeners == null)
            actionListeners = new Vector();
        if (!actionListeners.contains(listener))
            actionListeners.addElement(listener);
    }

    /**
     * Removes action listener from this component.
     * @param listener action listener
     */
    public void removeActonListener(ActionListener listener)
    {
        actionListeners.removeElement(listener);
    }

    /**
     * Adds mouse listener to this component.
     * @param listener mouse listener
     */
    public void addMouseListener(MouseListener listener)
    {
        if (mouseListeners == null)
            mouseListeners = new Vector();
        if (!mouseListeners.contains(listener))
            mouseListeners.addElement(listener);
    }

    /**
     * Removes mouse listener from this component.
     * @param listener mouse listener
     */
    public void removeMouseListener(MouseListener listener)
    {
        mouseListeners.removeElement(listener);
    }

    /**
     * Adds focus listener to this component.
     * @param listener focus listener
     */
    public void addFocusListener(FocusListener listener)
    {
        if (focusListeners == null)
            focusListeners = new Vector();
        if (!focusListeners.contains(listener))
            focusListeners.addElement(listener);
    }

    /**
     * Removes focus listener from this component.
     * @param listener mouse listener
     */
    public void removeFocusListener(FocusListener listener)
    {
        focusListeners.removeElement(listener);
    }

     /**
     * Adds key listener to this component.
     * @param listener key listener
     */
    public void addKeyListener(KeyListener listener)
    {
        if (keyListeners == null)
            keyListeners = new Vector();
        if (!keyListeners.contains(listener))
            keyListeners.addElement(listener);
    }

    /**
     * Removes key listener from this component.
     * @param listener key listener
     */
    public void removeKeyListener(KeyListener listener)
    {
        keyListeners.removeElement(listener);
    }

    /**
     * Displatches mouse event to listeners
     * @param mouseEvent mouse event
     */
    protected void dispatchEventToListeners(MouseEvent mouseEvent)
    {
        if (mouseListeners == null || mouseListeners.size() == 0) return;

        MouseEvent event = new MouseEvent(this, mouseEvent.getID(), mouseEvent.getX(), mouseEvent.getY(), mouseEvent.getValue());
        for (int i=0; i<mouseListeners.size(); i++)
        {
            MouseListener listener = (MouseListener) mouseListeners.elementAt(i);
            switch(event.getID())
            {
                case MouseEvent.MOUSE_PRESSED:
                    listener.mousePressed(event);
                    break;
                case MouseEvent.MOUSE_RELEASED:
                    listener.mouseReleased(event);
                    break;
                case MouseEvent.MOUSE_CLICKED:
                    listener.mouseClicked(event);
                    break;
                case MouseEvent.MOUSE_SCROLL_HORIZONTAL:
                case MouseEvent.MOUSE_SCROLL_VERTICAL:
                    listener.mouseScrolled(event);
                    break;
            }
        }
    }

    protected void dispatchEventToListeners(FocusEvent event)
    {
        if (focusListeners == null) return;

        for (int i=0; i<focusListeners.size(); i++)
        {
            FocusListener listener = (FocusListener) focusListeners.elementAt(i);
            switch (event.getID())
            {
                case FocusEvent.FOCUS_GAINED:
                    listener.focusGained(event);
                    break;
                case FocusEvent.FOCUS_LOST:
                    listener.focusLost(event);
                    break;
            }
        }
    }

    protected void dispatchEventToListeners(ActionEvent actionEvent)
    {
        if (actionListeners == null) return;

        for (int i=0; i<actionListeners.size(); i++)
        {
            ActionListener listener = (ActionListener) actionListeners.elementAt(i);
            listener.actionPerformed(actionEvent);
        }
    }

    protected void dispatchEventToListeners(KeyEvent keyEvent)
    {
        if (keyListeners == null) return;

        for (int i=0; i<actionListeners.size(); i++)
        {
            KeyListener listener = (KeyListener) keyListeners.elementAt(i);
            switch (keyEvent.getID())
            {
                case KeyEvent.KEY_PRESSED:
                    listener.keyPressed(keyEvent);
                    break;
                 case KeyEvent.KEY_RELEASED:
                    listener.keyReleased(keyEvent);
                    break;
                  case KeyEvent.KEY_REPEATED:
                    listener.keyRepeated(keyEvent);
                    break;
            }
        }
    }

    /**
     * Called every tick. Subclasses may override this method to do custom
     * actions that depend on real time.
     * @param dt time in milliseconds
     */

    public void tick(int dt)
    {
        if (!isVisible)
            return;

        if (!validated)
            validate();

        currentStyle.tick(dt);
    }

    /**
     * Save current graphics object translation parameters
     * @param g Graphics object
     */
    protected void saveTranslate(Graphics g)
    {
        oldTransX = g.getTranslateX();
        oldTransY = g.getTranslateY();
    }

    /**
     * Save current graphics object clipping parameters
     * @param g Graphics object
     */
    protected void saveClip(Graphics g)
    {
        oldClipX = g.getClipX();
        oldClipY = g.getClipY();
        oldClipWidth = g.getClipWidth();
        oldClipHeight = g.getClipHeight();
    }

    /**
     * Restore graphics object translation parameters
     * @param g Graphics object
     */
    protected void restoreTranslate(Graphics g)
    {
        g.translate(oldTransX - g.getTranslateX(), oldTransY - g.getTranslateY());
    }

    /**
     * Restore graphics object clipping parameters
     * @param g Graphics object
     */
    protected void restoreClip(Graphics g)
    {
        g.setClip(oldClipX, oldClipY, oldClipWidth, oldClipHeight);
    }

    /**
     * Adjusts translation parameters depending on content alignment hints.
     * Called by paint() method before paiting component's content.
     * @param g Graphics object
     */
    protected void applyContentAlignment(Graphics g)
    {
        int x = 0, y = 0;

        if ((contentAlignment & LEFT_ALIGNMENT) != 0)
        {
        }
        else if ((contentAlignment & HCENTER_ALIGNMENT) != 0)
        {
            x = (clientSize.width - contentSize.width) >> 1;
        }
        else if ((contentAlignment & RIGHT_ALIGNMENT) != 0)
        {
            x = clientSize.width - contentSize.width;
        }

        if ((contentAlignment & TOP_ALIGNMENT) != 0)
        {
        }
        else if ((contentAlignment & VCENTER_ALIGNMENT) != 0)
        {
            y = (clientSize.height - contentSize.height) >> 1;
        }
        else if ((contentAlignment & BOTTOM_ALIGNMENT) != 0)
        {
            y = clientSize.height - contentSize.height;
        }

        if (x < 0 || x >= clientSize.width) x = 0;
        if (y < 0 || y >= clientSize.height) y = 0;

        g.translate(x + insets[0], y + insets[2]);
    }

    /**
     * Paints component.
     * @param g Graphics object
     */
    public void paint(Graphics g)
    {
        if (!isVisible) return;

        //Paint decorations
        currentStyle.paint(g, 0, 0, getWidth(), getHeight());

        //Clip client area
        g.clipRect(insets[0], insets[2], clientSize.width, clientSize.height);

        //Apply content alignment
        applyContentAlignment(g);

        //Paint component
        paintComponent(g);
    }

    /**
     * Paints component. All subclasses must override this method
     * @param g graphics
     */
    abstract protected void paintComponent(Graphics g);

    /**
     * Causes application triggered repaint request
     */
    public void repaint()
    {
        repaint(0, 0, width, height);
    }

    /**
     * Dispatched repaint request
     * @param x area x
     * @param y area y
     * @param width area width
     * @param height area height
     */
    public void repaint(int x, int y, int width, int height)
    {
         if (parent != null/*this.isLightweight*/)
         {
             // Needs to be translated to parent coordinates since
             // a parent native container provides the actual repaint
             // services.  Additionally, the request is restricted to
             // the bounds of the component.
             //parent.getComponentBounds();

             //int[] parentInsets = parent.getInsets();

             int px = this.x + ((x < 0) ? 0 : x);// + parentInsets[0];
             int py = this.y + ((y < 0) ? 0 : y);// + parentInsets[2];
             int pwidth = (width > this.width) ? this.width : width;
             int pheight = (height > this.height) ? this.height : height;
             parent.repaint(px, py, pwidth, pheight);
         }
         else
         {
             PaintEvent e = new PaintEvent(this, new Rectangle(x, y, width, height));
             UIManager.postPaintEvent(e);
         }
    }

    /**
     * Assigns text label to component, to retrieve later from toString() method.
     * Used for debugging purposes only.
     * @param tag String label
     */
    public void setTag(String tag)
    {
        this.tag = tag;
    }

    public String toString()
    {
        return tag == null ? super.toString() : tag;
    }

    public Container getFocusCycleRootAncestor()
    {
        Container rootAncestor = (Container) this.parent;
        while (rootAncestor != null && !rootAncestor.isFocusCycleRoot())
            rootAncestor = (Container) rootAncestor.parent;

        return rootAncestor;
    }

    /**
     * Returns whether the specified Container is the focus cycle root of this
     * Component's focus traversal cycle. Each focus traversal cycle has only
     * a single focus cycle root and each Component which is not a Container
     * belongs to only a single focus traversal cycle.
     *
     * @param container the Container to be tested
     * @return <code>true</code> if the specified Container is a focus-cycle-
     *         root of this Component; <code>false</code> otherwise
     * @see Container#isFocusCycleRoot()
     */
    public boolean isFocusCycleRoot(Container container)
    {
        Container rootAncestor = getFocusCycleRootAncestor();
        return (rootAncestor == container);
    }

    public boolean isFocusOwner()
    {
        return UIManager.getFocusOwner() == this;
    }

    public boolean transferFocus()
    {
        Container rootAncestor = getFocusCycleRootAncestor();
        if (rootAncestor != null)
        {
            Component toFocus = rootAncestor.getComponentAfter(this);
            if (toFocus != null && toFocus != this)
            {
                return makeSureIsVisible(toFocus) ? toFocus.requestFocus() : true;
            }
            else return rootAncestor.transferFocusDownCycle();
        }

        return false;
    }

    public boolean transferFocusBackward()
    {
        Container rootAncestor = getFocusCycleRootAncestor();
        if (rootAncestor != null)
        {
            Component toFocus = rootAncestor.getComponentBefore(this);
            if (toFocus != null && toFocus != this)
            {
                return makeSureIsVisible(toFocus) ? toFocus.requestFocus() : true;
            }
            else return transferFocusUpCycle();
        }

        return false;
    }

    public boolean transferFocusUpCycle()
    {
        Container rootAncestor = getFocusCycleRootAncestor();
        if (rootAncestor != null)
        {
            return rootAncestor.transferFocusBackward();
        }
        
        return false;
    }

    protected boolean makeSureIsVisible(Component component)
    {
        Container scrollablePane = component.parent;

        int relative_x = component.x;
        int relative_y = component.y;

        while(scrollablePane != null)
        {
            while (scrollablePane != null && !(scrollablePane instanceof ScrollPane))
            {
                relative_x += scrollablePane.x;
                relative_y += scrollablePane.y;

                scrollablePane = scrollablePane.parent;
            }

            if (scrollablePane != null)
            {
                ScrollPane scrollPane = (ScrollPane) scrollablePane;
                
                int[] insets = scrollPane.getInsets();

                boolean isFullyVisibleLX = relative_x - insets[0] /*+ scrollPane.getHorizontalBar().getValue()*/ >= 0;
                boolean isFullyVisibleRX = relative_x - insets[0] + component.width /*+ scrollPane.getHorizontalBar().getValue()*/ <= scrollPane.clientSize.width;//scrollPane.getHorizontalBar().getVisibleAmount();
                boolean isFullyVisibleTY = relative_y - insets[2] /*+ scrollPane.getVerticalBar().getValue() */ >= 0;
                boolean isFullyVisibleBY = relative_y - insets[2] + component.height /*+ scrollPane.getVerticalBar().getValue()*/ <= scrollPane.clientSize.height;//scrollPane.getVerticalBar().getVisibleAmount();
                if (!isFullyVisibleLX) UIManager.postEvent(new MouseEvent(scrollPane, MouseEvent.MOUSE_SCROLL_HORIZONTAL, 0, 0, -5));
                else if (!isFullyVisibleRX) UIManager.postEvent(new MouseEvent(scrollPane, MouseEvent.MOUSE_SCROLL_HORIZONTAL, 0, 0, +5));
                if (!isFullyVisibleTY) UIManager.postEvent(new MouseEvent(scrollPane, MouseEvent.MOUSE_SCROLL_VERTICAL, 0, 0, -5));
                else if (!isFullyVisibleBY) UIManager.postEvent(new MouseEvent(scrollPane, MouseEvent.MOUSE_SCROLL_VERTICAL, 0, 0, +5));

                boolean isFullyVisible = isFullyVisibleLX & isFullyVisibleRX & isFullyVisibleTY & isFullyVisibleBY;
                if (!isFullyVisible) return false;

                relative_x += scrollPane.x;
                relative_y += scrollPane.y;
                //relative_y -= scrollPane.getVerticalBar().getValue();
                scrollablePane = scrollablePane.parent;
            }
        }
        
        return true;
    }

    /**
     * Requests UI manager to make this component a current focus owner
     * @return true if this component now a new focus owner, false - otherwise
     */
    public boolean requestFocus()
    {
        return isFocusable() && isEnabled ? UIManager.setMostRecentFocusOwner(this) : false;
    }
}


